package com.boarsoft.boar.sequence.snowflake;

import java.util.Calendar;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 分布式全局ID雪花算法解决方案
 *
 * 防止时钟回拨 因为机器的原因会发生时间回拨，我们的雪花算法是强依赖我们的时间的，如果时间发生回拨，
 * 有可能会生成重复的ID，在我们上面的nextId中我们用当前时间和上一次的时间进行判断， 如果当前时间小于上一次的时间那么肯定是发生了回拨，
 * 普通的算法会直接抛出异常,这里我们可以对其进行优化,一般分为两个情况:
 * 如果时间回拨时间较短，比如配置5ms以内，那么可以直接等待一定的时间，让机器的时间追上来。
 * 如果时间的回拨时间较长，我们不能接受这么长的阻塞等待，那么又有两个策略: 直接拒绝，抛出异常，打日志，通知RD时钟回滚。
 * 利用扩展位，上面我们讨论过不同业务场景位数可能用不到那么多，那么我们可以把扩展位数利用起来了，
 * 比如当这个时间回拨比较长的时候，我们可以不需要等待，直接在扩展位加1。
 * 2位的扩展位允许我们有3次大的时钟回拨，一般来说就够了，如果其超过三次我们还是选择抛出异常，打日志。
 * 通过上面的几种策略可以比较的防护我们的时钟回拨，防止出现回拨之后大量的异常出现。下面是修改之后的代码，这里修改了时钟回拨的逻辑:
 */
public class SnowflakeIdWorkerV2 {
	private static final Logger log = LoggerFactory.getLogger(SnowflakeIdWorkerV2.class);

	private String id;
	/**
	 *
	 */
	private static long sinceTimestamp = 1538211907857L;
	/**
	 * 机器ID偏左移12位
	 */
	private long workerIdShift;
	/**
	 * 时间毫秒左移22位（11+11）
	 */
	private long timestampLeftShift;
	/**
	 * sequence掩码，确保sequence不会超出上限 最大的序列号<br>
	 * 如：4096 -1 的补码（二进制全1）右移12位, 然后取反<br>
	 * 生成序列的掩码，这里为4095 (0b111111111111=0xfff=4095)
	 */
	private long sequenceMask;
	/**
	 * 机器ID，由公共服务管理
	 */
	private long workerId;
	/**
	 * 时钟编号
	 */
	private long clockId;
	/**
	 * 时钟移位
	 */
	private long clockIdShift;
	/**
	 * 当前毫秒生成的序列
	 */
	private volatile long sequence = 0L;

	private long lastTimestamp;

	public SnowflakeIdWorkerV2(String seqName, SnowflakeConfig config) {
		Calendar calendar = Calendar.getInstance();
		calendar.set(config.getSinceYear(), Calendar.JANUARY, 1);
		calendar.set(Calendar.HOUR_OF_DAY, 0);
		calendar.set(Calendar.MINUTE, 0);
		calendar.set(Calendar.SECOND, 0);
		calendar.set(Calendar.MILLISECOND, 0);
		// 服务器第一次上线时间点, 设置后不允许修改
		sinceTimestamp = calendar.getTimeInMillis();

		id = config.getId();
		workerIdShift = config.getSequenceBits();
		timestampLeftShift = config.getSequenceBits() + config.getWorkerIdBits();
		this.clockId=config.getClockId();
		this.clockIdShift=8;
		sequenceMask = ~(-1L << clockIdShift);
		workerId = config.workerId;
		lastTimestamp = System.currentTimeMillis();
		log.info("New snowflake worker {} of {}", workerId, seqName);
	}

	/**
	 * 获得下一个ID (该方法是线程安全的) 在单节点上获得下一个ID，使用Synchronized控制并发，而非CAS的方式，
	 * 是因为CAS不适合并发量非常高的场景。
	 *
	 * 考虑时钟回拨 缺陷: 如果连续两次时钟回拨, 可能还是会有问题, 但是这种概率极低极低
	 * 
	 * @return
	 */
	public synchronized long nextId() {
		long seq = (++sequence) & sequenceMask;
		//如果序号等于0
		if (seq == 0) {
			// 自旋等待到下一毫秒
			lastTimestamp = waitUntilNextTime(lastTimestamp);
		}
		// 时间戳（41位）| workerId（10位）| clockId（4位）| 序号（8位）
		return (lastTimestamp - sinceTimestamp) << timestampLeftShift | workerId << workerIdShift | clockId << clockIdShift | seq;
	}

	/**
	 * 阻塞到下一个毫秒，直到获得新的时间戳
	 * 
	 * @param lastTimestamp
	 *            上次生成ID的时间截
	 * @return 当前时间戳
	 */
	protected long waitUntilNextTime(long lastTimestamp) {
		long timestamp = System.currentTimeMillis();
		while (timestamp <= lastTimestamp) {
			timestamp = System.currentTimeMillis();
		}
		return timestamp;
	}

	public String getId() {
		return id;
	}
}